<script lang="ts">
import { Teleport, reactive, onMounted, onUnmounted, nextTick, computed, watch, h, ref, PropType } from 'vue'
import popIdUtil from "../common/popIdUtil"
import popLayerUtil from "../common/popLayerUtil"
import { createPopper } from '@popperjs/core'
import utils from '../common/utils'
import { getLastMouseDownEvent } from '../common/global'
import { PopModifier, PopPlacementType, TriggerType } from '@/types'
interface PropsType {
    popShown?: boolean
    popPlacement: PopPlacementType
    popModifiers: PopModifier[]
    getPopModifiersFunc?: () => PopModifier[]
    escToClose: boolean
    trigger: TriggerType
}
interface StateType {
    popId?: number
    resizeObserver?: ResizeObserver
    mutationObserver?: MutationObserver
    prepareToHide: boolean
    innerPopShown: boolean
    popInstance?: any
}
export default {
    props: {
        popShown: {
            type: Boolean,
            default: undefined
        },
        popPlacement: {
            type: String as PropType<PopPlacementType>,
            default: 'bottom'
        },
        popModifiers: {
            type: Array as PropType<PopModifier[]>,
            default: []
        },
        getPopModifiersFunc: {
            type: Function as PropType<() => PopModifier[]>,
            default: undefined
        },
        escToClose: {
            type: Boolean,
            default: true
        },
        trigger: {
            type: String as PropType<TriggerType>,
            default: 'click'
        },
    },
    setup(props: PropsType, ctx) {
        const state = reactive<StateType>({
            prepareToHide: false,
            innerPopShown: false
        })
        const myPopShown = computed(() => {
            if (props.popShown !== undefined) {
                return props.popShown
            }
            return state.innerPopShown
        })
        watch(() => myPopShown.value, () => {
            if (myPopShown.value) {
                methods.onPopShown()
            } else {
                methods.onPopHidden()
            }
        })
        watch(() => props.popModifiers, () => {
            state.popInstance?.setOptions({
                modifiers: methods.getPopModifiers()
            })
        })

        onMounted(() => {
            state.resizeObserver = new ResizeObserver(methods.pinToTarget)
            state.mutationObserver = new MutationObserver(methods.pinToTarget)
            methods.addTargetEventListener()
            if (myPopShown.value) {
                methods.onPopShown()
            }
        })
        onUnmounted(() => {
            methods.onPopHidden()
        })

        const targetRef = ref()
        const popRef = ref()
        const methods = {
            getTargetEl() {
                return utils.getEl(targetRef.value)
            },
            getPopEl() {
                return utils.getEl(popRef.value)
            },
            addEventListener() {
                window.document.addEventListener('mousedown', methods.onDocumentMousedown)
                if (props.escToClose) {
                    window.document.addEventListener('keydown', methods.onDocumentKeydown)
                }
            },
            removeEventListener() {
                window.document.removeEventListener('mousedown', methods.onDocumentMousedown)
                if (props.escToClose) {
                    window.document.removeEventListener('keydown', methods.onDocumentKeydown)
                }
            },
            onDocumentMousedown(e: MouseEvent) {
                let targetEl = methods.getTargetEl()!

                if (
                    popLayerUtil.isOpenedByMeOrMyChildren(targetEl, e.target as Node)
                    || targetEl.contains(e.target as Node)
                    || !window.document.contains(e.target as Node)
                ) {
                    return
                }
                let lastMouseDownEvent = getLastMouseDownEvent()!
                if (methods.isEventOfChildOfScrollbar(lastMouseDownEvent)) {
                    return
                }
                methods.hidePop()
            },
            isEventOfChildOfScrollbar(e: MouseEvent) {
                if (!e.path) {
                    let ele = e.target as HTMLElement
                    while (ele) {
                        if (ele.classList && ele.classList.contains('c-scrollbar')) {
                            return true
                        }
                        ele = ele.parentNode as HTMLElement
                    }
                    return false
                }
                return e.path.some(a => a && a.classList && a.classList.contains('c-scrollbar'))
            },
            onDocumentKeydown(e: KeyboardEvent) {
                if (e.code === 'esc' && popIdUtil.isTop(state.popId!)) {
                    methods.hidePop()
                }
            },
            onPopShown() {
                if (props.escToClose) {
                    state.popId = popIdUtil.create()
                }
                nextTick(() => {
                    methods.logLayerRelation()
                    methods.pinToTarget()
                    let targetEl = methods.getTargetEl()!
                    state.resizeObserver!.observe(targetEl)
                    state.mutationObserver!.observe(targetEl, { attributes: true, attributeFilter: ['style'] })
                    methods.addEventListener()
                    methods.addPopEventListener()
                })
            },
            onPopHidden() {
                methods.removeEventListener()

                popIdUtil.remove(state.popId!)
                methods.closeLayer()

                state.resizeObserver!.disconnect()
                state.mutationObserver!.disconnect()

                if (state.popInstance) {
                    state.popInstance.destroy()
                }
            },
            showPop() {
                state.innerPopShown = true
                ctx.emit('update:popShown', true)
            },
            hidePop() {
                state.innerPopShown = false
                ctx.emit('update:popShown', false)
            },
            logLayerRelation() {
                let popEl = methods.getPopEl()
                if (!popEl) {
                    return
                }

                let targetEl = methods.getTargetEl()!

                popLayerUtil.logLayerRelation(targetEl, popEl)
            },
            closeLayer() {
                let popEl = methods.getPopEl()
                if (!popEl) {
                    return
                }
                popLayerUtil.closeLayer(popEl)
            },
            pinToTarget() {
                let popEl = methods.getPopEl()
                if (!popEl) {
                    return
                }

                let targetEl = methods.getTargetEl()!

                if (state.popInstance) {
                    state.popInstance.destroy()
                }

                state.popInstance = createPopper(targetEl, popEl, {
                    strategy: 'fixed',
                    placement: props.popPlacement,
                    modifiers: methods.getPopModifiers()
                })
            },
            getPopModifiers() {
                let popModifiers = props.popModifiers
                if (props.getPopModifiersFunc) {
                    popModifiers = props.getPopModifiersFunc()
                }
                return popModifiers
            },
            addTargetEventListener() {
                let targetEl = methods.getTargetEl()!
                if (props.trigger === 'click') {
                    targetEl.addEventListener('click', methods.onClick)
                }
                else if (props.trigger === 'hover') {
                    targetEl.addEventListener('mouseenter', methods.onMouseenter)
                    targetEl.addEventListener('mouseleave', methods.onMouseleave)
                }
            },
            addPopEventListener() {
                let popEl = methods.getPopEl()!
                if (props.trigger === 'hover') {
                    popEl.addEventListener('mouseenter', methods.onMouseenter)
                    popEl.addEventListener('mouseleave', methods.onMouseleave)
                }
            },
            onClick() {
                if (myPopShown.value) {
                    methods.hidePop()
                } else {
                    methods.showPop()
                }
            },
            onMouseenter() {
                methods.showPop()
                state.prepareToHide = false
            },
            onMouseleave() {
                state.prepareToHide = true
                setTimeout(() => {
                    if (state.prepareToHide) {
                        methods.hidePop()
                    }
                }, 50)
            },
        }

        ctx.expose({
            getTargetEl: methods.getTargetEl,
            getPopEl: methods.getPopEl,
        })

        return () => {
            let targetAttrs = Object.assign({
                ref: targetRef,
            }, ctx.attrs)

            let popAttrs = {
                ref: popRef,
            }

            let elList = [h(ctx.slots.default!()[0], targetAttrs)]
            if (myPopShown.value) {

                let popRender = h(ctx.slots.pop!()[0], popAttrs)

                elList.push(h(Teleport, {
                    to: 'body'
                }, popRender))
            }
            return elList
        }
    }
}
</script>